3 转移线程所有权

一,std::move函数

void some_function(); void some_other_function(); std::thread t1(some_function); // 1 std::thread t2=std::move(t1); // 2 t1=std::thread(some_other_function); // 3 std::thread t3; // 4 t3=std::move(t2); // 5 t1=std::move(t3); // 6 赋值操作将使程序崩溃 首先,新线程开始与t1相关联。 显式使用std::move()创建t2后②,t1的所有权就转移给了t2。 之后,t1和执行线程已经没有关联了;执行some_function的函数现在与t2关联。 然后,与一个临时std::thread对象相关的线程启动了③。 为什么不显式调用std::move()转移所有权呢?因为,所有者是一个临时对象(非命名对象)——移动操作将会隐式的调用。 t3使用默认构造方式创建④,与任何执行线程都没有关联。调用std::move()将与t2关联线程的所有权转移到t3中⑤。 因为t2是一个命名对象,需要显式的调用std::move()。 移动操作⑤完成后,t1与执行some_other_function的线程相关联,t2与任何线程都无关联,t3与执行some_function的线程相关联。 最后一个移动操作,将some_function线程的所有权转移⑥给t1。 不过,t1已经有了一个关联的线程(执行some_other_function的线程),所以这里系统直接调用std::terminate()终止程序继续运行。 这样做(不抛出异常,std::terminate()是noexcept函数)是为了保证与std::thread的析构函数的行为一致。 2.1.1节中,需要在线程对象被析构前,显式的等待线程完成,或者分离它;进行赋值时也需要满足这些条件(说明:不能通过赋一个新值给std::thread对象的方式来”丢弃”一个线程)。

二,将std::thread放入std::vector std::thread对象的容器,如果这个容器是移动敏感的(比如,标准中的std::vector<>),那么移动操作同样适用于这些容器。了解这些后,就可以写出类似清单2.7中的代码,代码量产了一些线程,并且等待它们结束。 清单2.7 量产线程,等待它们结束 void do_work(unsigned id);

void f() { std::vectorstd::thread threads; for(unsigned i=0; i < 20; ++i) { threads.push_back(std::thread(do_work,i)); // 产生线程 } std::for_each(threads.begin(),threads.end(), std::mem_fn(&std::thread::join)); // 对每个线程调用join() } 我们经常需要线程去分割一个算法的总工作量,所以在算法结束的之前,所有的线程必须结束。清单2.7说明线程所做的工作都是独立的,并且结果仅会受到共享数据的影响。如果f()有返回值,这个返回值就依赖于线程得到的结果。在写入返回值之前,程序会检查使用共享数据的线程是否终止。操作结果在不同线程中转移的替代方案,我们会在第4章中再次讨论。 将std::thread放入std::vector是向线程自动化管理迈出的第一步:并非为这些线程创建独立的变量,并且将他们直接加入,可以把它们当做一个组。创建一组线程(数量在运行时确定),可使得这一步迈的更大,而非像清单2.7那样创建固定数量的线程。

标记: 二,“我们会在第4章中再次讨论”